#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR 0
#define INPUT_REDIR 1 
#define OUTPUT_REDIR 2 
#define APPEND_REDIR 3
#define trimSpace(start) do{\
            while(isspace(*start)) ++start;\
     }while(0)
//使用这样的写法是因为 while后面没有分号！所以不用担心这个函数会出问题！而且while(0)本身没有意义只会执行一次！
char LineCommand[NUM];
char* myargv[OPT_NUM];
int lastCode = 0;
int lastSig = 0;

int  redirType = NONE_REDIR;//开始的模式就是无操作！
char* redirFile = NULL;
//"ls -a -l -i > myfile.txt" 重定向的写法！
//首先要做的就是将字符串拆成 "ls -a -l -i" 和 "my.txt"
//切割的核心原理就是将字符串里面的>> / > / < 换成\0即可！
//这样子后续代码就不用改变！可以继续处理命令部分！
void commandCheck(char* commands)
{
    assert(commands);
    char* start = commands;
    char* end = commands + strlen(commands);//不用 + 1 刚刚好到 \0
    while(start <end)
    {
        if(*start == '>')
        {
            *start = '\0';
            ++start;
            if(*start == '>')
            {
                redirType  =APPEND_REDIR;
                ++start;
            }
            else 
            {
                redirType = OUTPUT_REDIR;
            }
            trimSpace(start);
            redirFile = start;
            break;
        }
        else if (*start == '<')
        {
            /* cat < file.txt */ 
            *start = '\0';
            ++start;
            trimSpace(start);
            //可以填写重定向信息！
            redirType  = INPUT_REDIR;
            redirFile = start;
            break;
        }
        else 
        {
            ++start;
        }
    }
}
int main()
{
    while(1)
    {
        //每一次都要对全局数据初始化！
        redirType = NONE_REDIR;//开始的模式就是无操作！
        redirFile = NULL;
        //打印输出提示符
        errno = 0;
        printf("用户名@主机名 当前路径# ");
        fflush(stdout);//刷新缓冲区！

        //获取用户输入，我们输入的时候会自己输入\n
        char* s = fgets(LineCommand,sizeof(LineCommand)-1,stdin);
        (void)s;
        assert (s != NULL);
        LineCommand[strlen(LineCommand)-1] = 0;//这是为了清楚最后一个\n!

        commandCheck(LineCommand);
        //"ls -a -l" -> "ls" "-a" "-l" 切割命令字串！
        myargv[0] = strtok(LineCommand," ");
        int i = 1;
        if(myargv[0] != NULL && strcmp(myargv[0],"ls")==0)
        {
            myargv[i++] = (char*)"--color=auto";
        }
        //strtok遇到空的返回值为NULL 同时要myargv要以NULL结尾！
        while(myargv[i++] = strtok(NULL," "));
        //如果是cd 指令就不需要创建子进程，让shell自己执行对应的命令！，本质就是调用系统接口！
        if(myargv[0] != NULL  && strcmp(myargv[0],"cd")==0)
        {
            if(myargv[1] != NULL)
            {
                chdir(myargv[1]);
            }
            continue;
        }
        //获取进程结束的结果
        if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0],"echo")==0 )
        {
            if(strcmp(myargv[1],"$?")==0)
            {
                printf("%d,%d\n",lastCode,lastSig);
            }
            else 
            {
                printf("%s\n",myargv[1]);
            }
            continue;
        }
#ifdef DEBUG 
        for(int i = 0;myargv[i];i++)
        {
            printf("myargv[%d]:%s\n",i,myargv[i]);
        }
#endif
        //执行命令！——让子进程执行！
        pid_t id = fork();
        assert(id != -1);
        if(id == 0)
        {
            //因为命令是子进程执行的！所以重定向的工作一定是子进程完成的！
            //如何重定向是有父进程给子进程提供信息的！
            //这里重定向会影响父进程吗？ ------- 不会！因为子进程也有自己的一张表！！ 而且进程具有独立性！
            switch(redirType)
            {
                case NONE_REDIR:
                    break;
                case INPUT_REDIR: 
                    {
                        int fd = open(redirFile,O_RDONLY);
                        //输入重定向只需要可读就行！因为是将一个文件里面的内容而不是键盘的内容导入另一个文件！
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        //重定向的文件已经打卡！
                        dup2(fd,0);
                    }
                    break;
                case OUTPUT_REDIR: 
                case APPEND_REDIR:
                    {
                        //为什么输出或在追加都匹配到这个呢？
                        //因为输出并不改变文件内容！所以可以和append公用同一个！
                        umask(0);
                        int flags = O_WRONLY | O_CREAT;
                        if(redirType == APPEND_REDIR) flags |= O_APPEND;
                        else flags |= O_TRUNC;//如果不是追加就是清除里面的内容！ 
                        int fd = open(redirFile,flags,0666);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        //重定向的文件已经成功打开了！
                        dup2(fd,1); 
                    }
                    break;
                default:
                    printf("bug?\n");
                    break;
            }
            execvp(myargv[0],myargv);
            //执行程序替换的时候会不会影响进程曾经进程打开的重定向的文件！
            //不会！程序替换的只是代码和数据！不会影响PCB和PIB的各种细节！
            exit(1);
        }
        int status = 0;
        pid_t ret =  waitpid(id,&status,0);
        (void)ret;
        assert(ret > 0);
        lastCode = ((status>>8)&0xFF);
        lastSig = status& 0x7F;
    }
    return 0;
}
